/******************************************************************
 * This program was written as part of
 *   Assignment 4
 *   Computer Graphics                                             
 *   Semester 1, 2013
 *   School of Computer Science,
 *   The University of Adelaide
 *                                                                          
 * We declare that all material in this assessment is our own work except where
 * there is clear acknowledgement and reference to the work of others. I have
 * read the University Policy Statement on Plagiarism, Collusion and Related
 * Forms of Cheating:  

 * http://www.adelaide.edu.au/policies/?230

 * We give permission for my assessment work, including our subversion
 * repository, to be reproduced and submitted to academic staff for the
 * purposes of assessment and to be copied, submitted and retained in a form
 * suitable for electronic checking of plagiarism.  
                            
 * Authors:
 *  Terry Moschou    a1208955
 *  Salih Al-Rashid  a1210108
 *  Ben McAleer      a1212203
*******************************************************************/

#include "Fire.hpp"
#include "Context.hpp"
#include "Material.hpp"

#include <glm/gtc/random.hpp>
#include <glm/gtc/constants.hpp>

Fire::Fire(Object * parent, glm::mat4 modelMatrix)
 : Object(parent, modelMatrix, 0, "Fire"),
   m_nParticles(1000),
   m_rate(0.01f),
   m_updateBuffer(A),
   m_renderBuffer(B),
   m_lifeTime(5.0f),
   m_accel(0.0f, -1.0f, 0.0f)
{
  //m_material = new Material("resources/models/fire.tga");
 
  Program * program = g_context.useProgram(Context::FireUpdate);
  
  m_ul_time0      = program->getUniformLocation("Time");
  m_ul_deltaTime  = program->getUniformLocation("H");
  m_ul_accel      = program->getUniformLocation("Accel");
  m_ul_lifeTime0  = program->getUniformLocation("ParticleLifetime");
  
  glUniform3fv(m_ul_accel, 3, &m_accel[0]);
  glUniform1f(m_ul_lifeTime0, m_lifeTime);
  
  program = g_context.useProgram(Context::FireRender);
  
  m_ul_time1      = program->getUniformLocation("Time");
  m_ul_lifeTime1  = program->getUniformLocation("ParticleLifetime");
  m_ul_model      = program->getUniformLocation("MatrixModel");

  glUniform1f(m_ul_lifeTime1, m_lifeTime);
  
  glGenBuffers(NumPasses * NumAttribs, m_vboHandles);
  glGenBuffers(1, &m_vboInitVel);
  
  unsigned vec3Size = sizeof(glm::vec3[m_nParticles]);
  unsigned floatSize = sizeof(float[m_nParticles]);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[A * NumAttribs + VertexPosition]);
  glBufferData(GL_ARRAY_BUFFER, vec3Size,  NULL, GL_DYNAMIC_COPY);
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[A * NumAttribs + StartTime]);
  glBufferData(GL_ARRAY_BUFFER, floatSize, NULL, GL_DYNAMIC_COPY);
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[A * NumAttribs + VertexVelocity]);
  glBufferData(GL_ARRAY_BUFFER, vec3Size,  NULL, GL_DYNAMIC_COPY);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[B * NumAttribs + VertexPosition]);
  glBufferData(GL_ARRAY_BUFFER, vec3Size,  NULL, GL_DYNAMIC_COPY);
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[B * NumAttribs + StartTime]);
  glBufferData(GL_ARRAY_BUFFER, floatSize, NULL, GL_DYNAMIC_COPY);
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[B * NumAttribs + VertexVelocity]);
  glBufferData(GL_ARRAY_BUFFER, vec3Size,  NULL, GL_DYNAMIC_COPY);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboInitVel);
  glBufferData(GL_ARRAY_BUFFER, vec3Size,  NULL, GL_STATIC_DRAW);

  glm::vec3 * vec3Data = new glm::vec3[m_nParticles];
  for (int i = 0; i < m_nParticles; ++i) {
    vec3Data[i].x = glm::linearRand(-1.0f, 1.0f);
    vec3Data[i].y = 0.0f;
    vec3Data[i].z = glm::linearRand(-1.0f, 1.0f);
    
  }
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[m_updateBuffer * NumAttribs + VertexPosition]);
  glBufferSubData(GL_ARRAY_BUFFER, 0, vec3Size, &vec3Data[0][0]);
  
  // Fill the first velocity buffer with random velocities
  for( int i = 0; i < m_nParticles; ++i) {
      vec3Data[i].x = 0.0f;
      vec3Data[i].y = glm::linearRand(0.01f, 0.1f);
      vec3Data[i].z = 0.0f;
  }
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[m_updateBuffer * NumAttribs + VertexVelocity]);
  glBufferSubData(GL_ARRAY_BUFFER, 0, vec3Size, &vec3Data[0][0]);
 
  glBindBuffer(GL_ARRAY_BUFFER, m_vboInitVel);
  glBufferSubData(GL_ARRAY_BUFFER, 0, vec3Size, &vec3Data[0][0]);
  
  delete [] vec3Data;
  float * floatData = new float[m_nParticles];
  
  
  float time = 0.0f;
  for(int i = 0; i < m_nParticles; ++i) {
    time += m_rate;
    floatData[i] = time;
  }
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[m_updateBuffer * NumAttribs + StartTime]);
  glBufferSubData(GL_ARRAY_BUFFER, 0, floatSize, floatData);
  //glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[m_renderBuffer * NumAttribs + StartTime]);
  //glBufferSubData(GL_ARRAY_BUFFER, 0, floatSize, floatData);

  glBindBuffer(GL_ARRAY_BUFFER,0);
  delete [] floatData;

  program = g_context.useProgram(Context::FireUpdate);
    
  m_al_Position0  = program->getAttributeLocation("VertexPosition");
  m_al_StartTime0 = program->getAttributeLocation("VertexStartTime");
  m_al_Velocity0  = program->getAttributeLocation("VertexVelocity");
  m_al_InitVel0   = program->getAttributeLocation("VertexInitialVelocity");
  
  
  glGenVertexArrays(2, m_vaoHandles);
  
  
  glBindVertexArray(m_vaoHandles[A]);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[A * NumAttribs + VertexPosition]);
  glVertexAttribPointer(m_al_Position0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_Position0);

  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[A * NumAttribs + StartTime]);
  glVertexAttribPointer(m_al_StartTime0, 1, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_StartTime0);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[A * NumAttribs + VertexVelocity]);
  glVertexAttribPointer(m_al_Velocity0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_Velocity0);

  glBindBuffer(GL_ARRAY_BUFFER, m_vboInitVel);
  glVertexAttribPointer(m_al_InitVel0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_InitVel0);
  
  
  program = g_context.useProgram(Context::FireRender);
    
  m_al_Position1  = program->getAttributeLocation("VertexPosition");
  //m_al_Velocity1  = program->getAttributeLocation("VertexVelocity");
  m_al_StartTime1 = program->getAttributeLocation("VertexStartTime");
  //m_al_InitVel1   = program->getAttributeLocation("VertexInitialVelocity");
  
  glGenVertexArrays(1, &m_vaoHandles[B]);
  glBindVertexArray(m_vaoHandles[B]);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[B * NumAttribs + VertexPosition]);
  glVertexAttribPointer(m_al_Position0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_Position0);

  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[B * NumAttribs + StartTime]);
  glVertexAttribPointer(m_al_StartTime0, 1, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_StartTime0);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[B * NumAttribs + VertexVelocity]);
  glVertexAttribPointer(m_al_Velocity0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_Velocity0);

  glBindBuffer(GL_ARRAY_BUFFER, m_vboInitVel);
  glVertexAttribPointer(m_al_InitVel0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(m_al_InitVel0);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
  
  
  glGenTransformFeedbacks(NumPasses, m_tfoHandles);
  
  glBindTransformFeedback(GL_TRANSFORM_FEEDBACK,                  m_tfoHandles[A]);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, m_al_Position0,  m_vboHandles[A * NumAttribs + VertexPosition]);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, m_al_StartTime0,  m_vboHandles[A * NumAttribs + StartTime]);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, m_al_Velocity0,  m_vboHandles[A * NumAttribs + VertexVelocity]);
  
  
  glBindTransformFeedback(GL_TRANSFORM_FEEDBACK,                  m_tfoHandles[B]);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, m_al_Position0,  m_vboHandles[B * NumAttribs + VertexPosition]);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, m_al_StartTime0,  m_vboHandles[B * NumAttribs + StartTime]);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, m_al_Velocity0,  m_vboHandles[B * NumAttribs + VertexVelocity]);
  

  glBindTransformFeedback(GL_TRANSFORM_FEEDBACK,                  0);
  
  glGenQueries(1, &m_query);
  
  
  
}

void Fire::draw(const glm::mat4 & modelMatrix)
{
  
  
  ///////////////// UPDATE PASS //////////////////////////////
  Program * program = g_context.useProgram(Context::FireUpdate);
  
  //glDisable(GL_DEPTH_TEST);
  glPointSize(10.0f);
  
  
  float newElapsedTime = float(glutGet(GLUT_ELAPSED_TIME))/1000.0f;
  float deltaTime = newElapsedTime - m_elapsedTime;
  m_elapsedTime = newElapsedTime;
    
  glUniform1f(m_ul_time0,     m_elapsedTime);
  glUniform1f(m_ul_deltaTime, deltaTime);
  
  
  // Disable rendering
  glEnable(GL_RASTERIZER_DISCARD);
  
  // Bind the feedback object for the buffers to be drawn next
  glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_tfoHandles[m_renderBuffer]);
  glBindVertexArray(m_vaoHandles[m_updateBuffer]);
  
  
  glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, m_query);
    // Draw points from input buffer with transform feedback
    glBeginTransformFeedback(GL_POINTS);
      
      glDrawArrays(GL_POINTS, 0, m_nParticles);
      //checkError();
    glEndTransformFeedback();
    //checkError();
  glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

  GLuint ptsWritten;
  glGetQueryObjectuiv(m_query, GL_QUERY_RESULT, &ptsWritten);
  //DEBUG("Written: %d\n", ptsWritten);
  
  //////////// Render pass ///////////////
  // Enable rendering
  glDisable(GL_RASTERIZER_DISCARD);

  program = g_context.useProgram(Context::FireRender);
  //glClear(GL_COLOR_BUFFER_BIT);
  glEnable(GL_POINT_SMOOTH);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
  glm::mat4 newModelMatrix = modelMatrix * m_modelMatrix;
  glUniformMatrix4fv(m_ul_model, 1, false,
                     &newModelMatrix[0][0]);
  glUniform1f(m_ul_time1,     m_elapsedTime);
  
  glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
  glBindVertexArray(m_vaoHandles[m_renderBuffer]);
  glDrawArrays(GL_POINTS, 0,  m_nParticles);
  
  m_renderBuffer = Buffers(1 - m_renderBuffer);
  m_updateBuffer = Buffers(1 - m_updateBuffer);
  
  glBindVertexArray(0);
  
  ///glEnable(GL_DEPTH_TEST);
  //glPointSize(10.0f);
  glDisable(GL_BLEND);
  //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  
}


















